home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue47 / IntBase / UnitQueryDB.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-05-03  |  16.3 KB  |  496 lines

  1. unit UnitQueryDB;
  2. (*********************************************************
  3.  
  4.   This unit demonstrates some basic DSQL techniques
  5.  
  6.   Most, if not all the routines could be defined as
  7.   part of the frs_GDS object, giving us a one stop 
  8.   shop for basic database, transaction and DSQL. 
  9.   This would make the class a bit more complex and 
  10.   we really ought to have separate classes for
  11.   each anyway. However, that leads to more coding - 
  12.   we would need to write ways for each class to 
  13.   communicate.
  14.  
  15.   For now, lets just keep things fairly simple and 
  16.   demonstrate the techniques.
  17.  
  18. **********************************************************)
  19.  
  20. interface
  21.  
  22. uses SysUtils, frs_Ibase, frs_Ibase_Object, frs_IBStartParams, math;
  23.  
  24. Type
  25.   TStatementType= ( stUnknown,stSelect,stInsert,stUpdate,stDelete,
  26.                               stDDL,stGetSegment,stPutSegment,stExecProcedure,stStartTrans,
  27.                                   stCommit,stRollback,stSelectForUpdate,stSetGenerator
  28.                   );
  29.  
  30.   TDsqlExecType = ( dsqlUnknown, dsqlExecImmediate, dsqlExecNoParams, dsqlExecParams,
  31.                      dsqlQueryNoParams, dsqlQueryParams);
  32.  
  33. Procedure AssignParam(AParam: String; Position: Integer);
  34. Procedure ExecuteStatement;
  35. Procedure PrepareStatement(SQLString: String);
  36. Function ReadRow: String;
  37. Function ReadTitles: String;
  38. Procedure UnprepareStatement;
  39.  
  40. implementation
  41.  
  42. function AdjustScale(Value: Integer; Scale: Integer): Double;
  43. //this bit blithely lifted from Greg Deatz' FreeIBComponents.
  44. var
  45.   Scaling, i: Integer;
  46.   Val: Double;
  47. begin
  48.   Scaling := 1; 
  49.   Val := Value;
  50.   if Scale > 0 then begin
  51.     for i := 1 to Scale do 
  52.       Scaling := Scaling * 10;
  53.     result := Val * Scaling;
  54.     end 
  55.   else 
  56.     if Scale < 0 then begin
  57.       for i := -1 downto Scale do 
  58.         Scaling := Scaling * 10;
  59.       result := Val / Scaling;
  60.       end 
  61.     else
  62.       result := Val;    
  63. end;
  64.  
  65. function GetStatementType(StatementHandle: pisc_stmt_handle): TStatementType;
  66. var
  67.   dsql_info  : Char;
  68.   res_buffer : array[0..7] of Char;
  69.   len        : integer;
  70. begin
  71. Result:=stUnknown;
  72. with frs_GDS do begin
  73.   if assigned(StatementHandle) then begin
  74.     fillChar(dsql_info,sizeof(dsql_info),#1);
  75.     fillchar(res_Buffer,SizeOf(res_Buffer),#1);
  76.  
  77.     dsql_info:=char(isc_info_sql_stmt_type);
  78.     Errorcode:=isc_dsql_sql_info(@StatusVector,@StmtHandle,SizeOf(dsql_info),
  79.                             @dsql_info,SizeOf(res_Buffer),@res_Buffer);
  80.  
  81.     if (res_buffer[0]=dsql_info) then begin
  82.       len := isc_vax_integer(@res_buffer[1], 2);
  83.       Result:=TStatementType(isc_vax_integer(@res_buffer[3],len));
  84.     end;
  85.   end;
  86. end;
  87.  
  88. end;
  89.  
  90. function GetDsqlExecType: TDsqlExecType;
  91. //This will return erroneous data if the statement has not been prepared
  92. //and memory not properly allocated for params and results.
  93.  
  94. begin
  95.   result:=dsqlUnknown;
  96. with frs_GDS do  
  97.   if (inputDataArea^.sqld=0) and (OutputDataArea^.sqld=0) then
  98.     result:=dsqlExecNoParams
  99.   else
  100.     if (inputDataArea^.sqld>0) and (OutputDataArea^.sqld=0) then
  101.       result:=dsqlExecParams
  102.     else
  103.       if (inputDataArea^.sqld=0) and (OutputDataArea^.sqld>0) then
  104.         result:=dsqlQueryNoParams
  105.       else
  106.         if (inputDataArea^.sqld>0) and (OutputDataArea^.sqld>0) then
  107.           result:=dsqlQueryParams;
  108. end;
  109.  
  110. function Pad(instr: string; len: integer): string;
  111. {pad a string with spaces and return it, or reduce it to len}
  112. begin
  113. result:=instr;
  114. if (length(result)>len) then
  115.   setlength(result,len)
  116. else
  117.   while length(result)<len do
  118.     result:=concat(result,' ');
  119. end;
  120.  
  121. function LeftPad(instr: string; len: integer): string;
  122. { Prepend a string with spaces and return it, or reduce it to len
  123.   Typically used to right justify numerics}
  124. begin
  125. result:=instr;
  126. if (length(result)>len) then
  127.   setlength(result,len)
  128. else
  129.   while length(result)<len do
  130.     result:=concat(' ',result);
  131. end;
  132.     
  133. Procedure AssignParam(AParam: String; Position: Integer);
  134. //This code has been structured to handle almost any datatype
  135. //and has been left intact, even though the example is only
  136. //dealing with a single char. 
  137. var
  138.   datatype: SmallInt;
  139.   s: String;
  140.   DateTime : TM;
  141.   DT: TDateTime;
  142.   Yr, Mn, Dy, Hr, Mt, Sc, Ms: Word;
  143. const
  144.   len: integer=0;
  145. begin
  146.   with frs_GDS, InPutDataArea^ do begin
  147.     datatype:= sqlvar[Position].sqltype and (not SQL_NULL);
  148.     case DataType of
  149.       SQL_Short   : begin                      
  150.                       len:=sqlvar[Position].sqllen;
  151.                       PSmallInt(sqlvar[Position].SQlData)^:=StrToInt(AParam);
  152.                       {$ifdef debug}  
  153.                       s:=IntToStr(PSmallInt(sqlvar[Position].SQLData)^)
  154.                       {$endif}
  155.                     end;
  156.       SQL_Long    : begin                      
  157.                       len:=sqlvar[Position].sqllen;
  158.                       PInteger(sqlvar[Position].SQlData)^:=StrToInt(AParam);
  159.                       {$ifdef debug}  
  160.                       s:=IntToStr(PInteger(sqlvar[Position].SQLData)^)
  161.                       {$endif}
  162.                     end;
  163.       SQL_Date    : begin                      
  164.                       len:=sqlvar[Position].sqllen;
  165.                       s:=AParam;
  166.                       DT:=IBDateStrToDateTime(s);
  167.                       DecodeDate(DT, Yr, Mn, Dy);
  168.                       DecodeTime(DT, Hr, Mt, Sc, Ms);
  169.                       with DateTime do begin
  170.                         sec := Sc;
  171.                         min := Mt;
  172.                         hour := Hr;
  173.                         mday := Dy;
  174.                         mon := Mn - 1;
  175.                         year := Yr - cYearOffset;
  176.                       end;
  177.                       isc_encode_date(@DateTime, PISC_QUAD(sqlvar[Position].sqldata));
  178.                     end;
  179.       SQL_VARYING : begin
  180.                       s:=AParam;
  181.                       len:=length(s);
  182.                       sqlvar[Position].sqltype := SQL_TEXT;
  183.                       fillchar(sqlvar[Position].SQlData^,sqlvar[Position].sqllen,#0);
  184.                       sqlvar[Position].sqllen:=len;
  185.                       move(s[1],sqlvar[Position].SQlData^,len);
  186.                       {$ifdef debug}  
  187.                       s:=pchar(sqlvar[Position].SQlData);
  188.                       len:=length(s);
  189.                       {$endif}
  190.                     end;
  191.       SQL_TEXT    : begin
  192.                       s:=AParam;
  193.                       len:=length(s);
  194.                       fillchar(sqlvar[Position].SQlData^,sqlvar[Position].sqllen,#0);
  195.                       sqlvar[Position].sqllen:=len;
  196.                       move(s[1],sqlvar[Position].SQlData^,len);
  197.                       {$ifdef debug}  
  198.                       s:=pchar(sqlvar[Position].SQlData);
  199.                       len:=length(s);
  200.                       {$endif}
  201.                     end;
  202.                       
  203.       //Support these datatypes is left as an exercise for the reader
  204.       SQL_FLOAT   : begin
  205.                     ;
  206.                     end;
  207.       SQL_DOUBLE  : begin
  208.                     ;
  209.                     end;
  210.  
  211.       //These require special processing. Not for the faint hearted              
  212.       SQL_BLOB    : begin
  213.                     ;
  214.                     end;
  215.       SQL_ARRAY   : begin
  216.                     ;
  217.                     end;
  218.  
  219.       //These should not normally be be used, but are theoretically possible.
  220.       SQL_QUAD    : begin
  221.                     ;
  222.                     end;
  223.       SQL_D_FLOAT : begin
  224.                     ;
  225.                     end;
  226.     end;
  227.   end;
  228. end;
  229.  
  230. Procedure ReadColumn(var AColumn: String; ColNo: Integer);
  231. var
  232.   datatype: SmallInt;
  233.   sqllen: Integer;
  234.   datalen: Integer;
  235.   s: String;
  236.   DateTime : TM;
  237.   pascalDateTime: TDateTime;
  238. begin
  239. with frs_GDS, OutPutDataArea^ do try
  240.   if (sqlvar[ColNo].sqlind^ = -1) then
  241.     AColumn:='(NULL)'
  242.   else begin
  243.     datatype:= sqlvar[ColNo].sqltype and (not SQL_NULL);
  244.     case DataType of
  245.       SQL_FLOAT   : AColumn:=FormatFloat('#,##0.00##',PSingle(sqlvar[ColNo].SQLData)^);
  246.       SQL_DOUBLE  : AColumn:=FormatFloat('#,##0.00##',PDouble(sqlvar[ColNo].SQLData)^);
  247.       SQL_SHORT   : if sqlvar[ColNo].sqlscale=0 then
  248.                       AColumn:=IntToStr(PSmallInt(sqlvar[ColNo].SQLData)^)
  249.                     else 
  250.                       AColumn:=FormatFloat('#,##0.00##',AdjustScale(PSmallInt(sqlvar[ColNo].SQLData)^,sqlvar[ColNo].sqlscale));
  251.  
  252.       SQL_LONG    : if sqlvar[ColNo].sqlscale=0 then
  253.                       AColumn:=IntToStr(PInteger(sqlvar[ColNo].SQLData)^)
  254.                     else
  255.                       AColumn:=FormatFloat('#,##0.00##',AdjustScale(PInteger(sqlvar[ColNo].SQLData)^,sqlvar[ColNo].sqlscale));
  256.       SQL_DATE    : begin
  257.                       isc_Decode_Date(pisc_quad(sqlvar[ColNo].SqlData),@DateTime);
  258.                       PascalDateTime:=EncodeDate(DateTime.year + cYearOffset,DateTime.mon +1,DateTime.mday);
  259.                       PascalDateTime:=PascalDateTime+EnCodeTime(DateTime.hour,DateTime.min,DateTime.Sec,0);
  260.                       AColumn:=FormatDateTime('dd-mmm-yyyy hh:nn:ss',PascalDateTime);
  261.                       end;
  262.       SQL_VARYING : begin
  263.                       datalen:=frs_GDS.isc_vax_integer(sqlvar[ColNo].SQlData,2);
  264.                       s:=pchar(sqlvar[ColNo].SQlData)+2;
  265.                       s:=Copy(S,1,Datalen);
  266.                       AColumn:=s;
  267.                     end;
  268.       SQL_TEXT    : begin
  269.                       sqllen:=sqlvar[ColNo].Sqllen;
  270.                       s:=pchar(sqlvar[ColNo].SQlData);
  271.                       s:=Copy(S,1,Sqllen);
  272.                       AColumn:=s;
  273.                     end;
  274.       SQL_BLOB    : begin
  275.                       If assigned(PISC_QUAD(sqlvar[ColNo].SQlData^)) then
  276.                         AColumn:='(BLOB)'
  277.                       else 
  278.                         AColumn:='(Blob)';
  279.                     end;
  280.       SQL_ARRAY   : AColumn:='(Array)';
  281.       SQL_D_FLOAT : AColumn:='(D_Float)';
  282.       SQL_QUAD    : AColumn:='(Quad)';
  283.     else
  284.       AColumn:='(unknown)';
  285.     end;
  286.   end; //else begin
  287.  
  288.   //now pad to length of title
  289.   datalen:=sqlvar[ColNo].sqllen;
  290.   if sqlvar[ColNo].sqllen>sqlvar[ColNo].aliasname_length then
  291.     AColumn:=pad(AColumn,sqlvar[ColNo].sqllen)
  292.   else 
  293.     AColumn:=pad(AColumn,sqlvar[ColNo].aliasname_length);
  294.  
  295. except
  296.   AColumn:='(Unknown)';
  297. end; 
  298.  
  299.  
  300. end;
  301.  
  302. Function ReadTitles: String;
  303. var
  304.   StatementType: TStatementType;
  305.   i: Integer;
  306.   datalen: Integer;
  307.   s: String;
  308. begin
  309. result:='';
  310. with frs_GDS do begin
  311.  
  312.   StatementType:=GetStatementType(@stmtHandle);
  313.   
  314.   if (StatementType=stExecProcedure) or (StatementType=stSelect) then
  315.     with OutputDataArea^ do 
  316.       for i:=0 to sqln-1 do begin
  317.         s:=copy(string(sqlvar[i].aliasname),1,sqlvar[i].aliasname_length);
  318.         datalen:=sqlvar[i].sqllen;
  319.         if sqlvar[i].sqllen>sqlvar[i].aliasname_length then
  320.           s:=pad(s,sqlvar[i].sqllen)
  321.         else 
  322.           s:=pad(s,sqlvar[i].aliasname_length);
  323.             
  324.         if result='' then
  325.           Result:=s
  326.         else
  327.           Result:=Result+' '+s;
  328.    
  329.       end;
  330.  
  331. end; //with frs_GDS
  332. end;
  333.  
  334.  
  335. Function ReadRow: String;
  336. var
  337.   StatementType: TStatementType;
  338.   i: Integer;
  339.   datalen: Integer;
  340.   s: String;
  341. begin
  342. result:='';
  343. with frs_GDS do begin
  344.  
  345.   StatementType:=GetStatementType(@stmtHandle);
  346.   
  347.   case StatementType of
  348.     stExecProcedure : FetchCode:= 0; //we already have the data in the outputdataarea - dont try to fetch it!!!!
  349.     stSelect        : FetchCode:=isc_dsql_fetch(@StatusVector, @StmtHandle, 1, OutPutDataArea);
  350.   else
  351.     fetchcode:=100;      
  352.   end;
  353.   
  354.   //if row was fetched then process it
  355.   if Fetchcode = 0 then 
  356.     with OutputDataArea^ do
  357.       for i:=0 to sqln-1 do begin
  358.         ReadColumn(s,i);
  359.         if result='' then
  360.           Result:=s
  361.         else
  362.           Result:=Result+' '+s;
  363.       end;
  364.   
  365.   if (StatementType=stExecProcedure) then 
  366.     FetchCode:= 100; 
  367.  
  368.  
  369. end; //with frs_GDS
  370. end;
  371.  
  372.  
  373. Procedure ExecuteStatement;
  374. {Executing a statement is not straightforward. 
  375.   There are four types of statement. 
  376.   Additionally, we have Stored procedures to worry about.
  377.  
  378.   Beware: Little testing has been done with parameterised stored procedures.
  379.  
  380.   Warning: 
  381.     No coding example is here to deal statement types of stCommit..stRetaining.
  382.     These statements require special care, or rejection, as there are api calls for them.
  383. }
  384. begin
  385. with frs_GDS do 
  386.   case GetDsqlExecType of
  387.     dsqlExecNoParams  : if (GetStatementType(@stmtHandle) = stExecProcedure) then
  388.                           //if values are returned they are placed in the OutputDataArea immediately
  389.                           ErrorCode:=isc_dsql_execute2(@StatusVector,@TxnHandle,@stmtHandle,1,nil,OutPutDataArea)
  390.                         else
  391.                           ErrorCode:=isc_dsql_execute(@StatusVector,@TxnHandle,@stmtHandle,1,nil);
  392.  
  393.     dsqlExecParams    : if (GetStatementType(@stmtHandle) = stExecProcedure) then
  394.                           ErrorCode:=isc_dsql_execute2(@StatusVector,@TxnHandle,@stmtHandle,1,InputDataArea,OutPutDataArea)
  395.                         else    
  396.                           ErrorCode:=isc_dsql_execute(@StatusVector,@TxnHandle,@StmtHandle,1,InPutDataArea);
  397.                         
  398.     dsqlQueryNoParams : if (GetStatementType(@stmtHandle) = stExecProcedure) then
  399.                           ErrorCode:=isc_dsql_execute2(@StatusVector,@TxnHandle,@StmtHandle,1,nil,OutPutDataArea)
  400.                         else
  401.                           ErrorCode:=isc_dsql_execute(@StatusVector,@TxnHandle,@StmtHandle,1,nil);
  402.  
  403.     dsqlQueryParams   : if (GetStatementType(@stmtHandle) = stExecProcedure) then
  404.                           ErrorCode:=isc_dsql_execute2(@StatusVector,@TxnHandle,@StmtHandle,1,nil,OutPutDataArea)
  405.                         else
  406.                           ErrorCode:=isc_dsql_execute(@StatusVector,@TxnHandle,@StmtHandle,1,InputDataArea);
  407.  
  408.   end;
  409.  
  410. end;
  411.  
  412. Procedure PrepareStatement(SQLString: String);
  413. { This is a generic routine that does more than always necessary.
  414.  
  415. We do several things here
  416.   1/ Allocate Statement Handle
  417.   2/ Prepare it
  418.   3/ Find out about parameters to it
  419.   4/ Allocate memory for the parameters.
  420.   5/ Find out about the columns in the result set
  421.   6/ Allocate memory to store each column in the result set
  422.  
  423.   If your statement doesn't use params or return a result set then 
  424.   some parts may be skipped.
  425. }
  426. var
  427.   i: Integer;
  428. begin
  429. with frs_GDS do begin
  430.  
  431.   //In real world, we might want to reconsider if the XSQLDAs are not NIL to start with.
  432.   if not assigned(InputDataArea) then
  433.     InitSQLDA(InputDataArea,1);
  434.  
  435.   if not assigned(OutputDataArea) then
  436.     InitSQLDA(OutputDataArea,1);
  437.   
  438.   //get a statement handle
  439.   //(In real world, we should check that the StmtHandle is NIL - if not nil, then why? Is it active?)
  440.   Errorcode:=isc_dsql_allocate_statement(@StatusVector,@DBHandle,@StmtHandle);
  441.  
  442.   Errorcode:=isc_dsql_prepare(@StatusVector,@TxnHandle,@StmtHandle,0,PChar(SQLString),1,OutputDataArea);
  443.  
  444.   {Here we will go through the motions of setting up for a parameterized query. 
  445.   More sophisticated code could check ahead and remove these calls if unnecessary.}
  446.  
  447.   //call describe_bind to find out how many params there are
  448.   ErrorCode:=isc_dsql_describe_bind(@StatusVector,@StmtHandle,1,InputDataArea);
  449.  
  450.   //read the number of params in the statement
  451.   i:=InPutDataArea^.sqld;
  452.  
  453.   //now allocate space for that many params
  454.   InitSQLDA(InPutDataArea,i);
  455.   ErrorCode:=isc_dsql_describe_bind(@StatusVector,@StmtHandle,1,InputDataArea);
  456.  
  457.   //allocate memory for the params
  458.   AllocateSQLData(InPutDataArea);
  459.  
  460.   //Now look at the results - again we could check ahead and skip this if not a select statement
  461.  
  462.   //find out how many columns in the result set
  463.   i:=OutPutDataArea^.sqld;
  464.  
  465.   //re-initialize XSQLDA for these columns
  466.   InitSQLDA(OutPutDataArea,i);
  467.   
  468.   //prepare has already laid the groundwork so we only need to call describe once
  469.   ErrorCode:=isc_dsql_describe(@StatusVector,@StmtHandle,1,OutPutDataArea);
  470.  
  471.   //now allocate the memory to hold the result
  472.   AllocateSQLData(OutPutDataArea);  
  473.   
  474. end;
  475.   
  476. end;
  477.  
  478. Procedure UnprepareStatement;
  479. begin
  480.  
  481.   with frs_GDS do begin
  482.     if assigned(StmtHandle) then begin
  483.       ErrorCode:=isc_dsql_free_statement(@StatusVector, @StmtHandle, DSQL_Drop);
  484.       StmtHandle:=nil;
  485.     end;
  486.  
  487.     FreeSQLData(InputDataArea);
  488.     FreeSQLData(OutputDataArea);
  489.  
  490.   end;
  491.   
  492. end;
  493.  
  494.  
  495. end.
  496.